/*
 * This is the source code of Telegram for Android v. 1.7.x.
 * It is licensed under GNU GPL v. 2 or later.
 * You should have received a copy of the license in this archive (see LICENSE).
 *
 * Copyright Nikolai Kudashov, 2013-2014.
 */

package org.telegram.android;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.media.ExifInterface;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;

import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.TLObject;
import org.telegram.messenger.TLRPC;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.ApplicationLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;

public class ImageLoader {

    private HashMap<String, Integer> bitmapUseCounts = new HashMap<>();
    private LruCache memCache;
    private HashMap<String, CacheImage> imageLoadingByUrl = new HashMap<>();
    private HashMap<String, CacheImage> imageLoadingByKeys = new HashMap<>();
    private HashMap<Integer, CacheImage> imageLoadingByTag = new HashMap<>();
    private HashMap<String, ThumbGenerateInfo> waitingForQualityThumb = new HashMap<>();
    private HashMap<Integer, String> waitingForQualityThumbByTag = new HashMap<>();
    private LinkedList<HttpImageTask> httpTasks = new LinkedList<>();
    private DispatchQueue cacheOutQueue = new DispatchQueue("cacheOutQueue");
    private DispatchQueue cacheThumbOutQueue = new DispatchQueue("cacheThumbOutQueue");
    private DispatchQueue thumbGeneratingQueue = new DispatchQueue("thumbGeneratingQueue");
    private DispatchQueue imageLoadQueue = new DispatchQueue("imageLoadQueue");
    private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue");
    private ConcurrentHashMap<String, Float> fileProgresses = new ConcurrentHashMap<>();
    private HashMap<String, ThumbGenerateTask> thumbGenerateTasks = new HashMap<>();
    private int currentHttpTasksCount = 0;

    private LinkedList<HttpFileTask> httpFileLoadTasks = new LinkedList<>();
    private HashMap<String, HttpFileTask> httpFileLoadTasksByKeys = new HashMap<>();
    private HashMap<String, Runnable> retryHttpsTasks = new HashMap<>();
    private int currentHttpFileLoadTasksCount = 0;

    protected VMRuntimeHack runtimeHack = null;
    private String ignoreRemoval = null;

    private volatile long lastCacheOutTime = 0;
    private int lastImageNum = 0;
    private long lastProgressUpdateTime = 0;

    private File telegramPath = null;

    private class ThumbGenerateInfo {
        private int count;
        private TLRPC.FileLocation fileLocation;
        private String filter;
    }

    private class HttpFileTask extends AsyncTask<Void, Void, Boolean> {

        private String url;
        private File tempFile;
        private String ext;
        private RandomAccessFile fileOutputStream = null;
        private boolean canRetry = true;

        public HttpFileTask(String url, File tempFile, String ext) {
            this.url = url;
            this.tempFile = tempFile;
            this.ext = ext;
        }

        protected Boolean doInBackground(Void... voids) {
            InputStream httpConnectionStream = null;
            boolean done = false;

            URLConnection httpConnection = null;
            try {
                URL downloadUrl = new URL(url);
                httpConnection = downloadUrl.openConnection();
                httpConnection.setConnectTimeout(5000);
                httpConnection.setReadTimeout(5000);
                httpConnection.connect();
                httpConnectionStream = httpConnection.getInputStream();

                fileOutputStream = new RandomAccessFile(tempFile, "rws");
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            try {
                if (httpConnection != null && httpConnection instanceof HttpURLConnection) {
                    int code = ((HttpURLConnection) httpConnection).getResponseCode();
                    if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
                        canRetry = false;
                    }
                }
            } catch (Exception e) {
                FileLog.e("tmessages", e);
            }

            try {
                byte[] data = new byte[1024 * 4];
                while (true) {
                    if (isCancelled()) {
                        break;
                    }
                    try {
                        int readed = httpConnectionStream.read(data);
                        if (readed > 0) {
                            fileOutputStream.write(data, 0, readed);
                        } else if (readed == -1) {
                            done = true;
                            break;
                        } else {
                            break;
                        }
                    } catch (Exception e) {
                        FileLog.e("tmessages", e);
                        break;
                    }
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                    fileOutputStream = null;
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            try {
                if (httpConnectionStream != null) {
                    httpConnectionStream.close();
                }
                httpConnectionStream = null;
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            return done;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            runHttpFileLoadTasks(this, result ? 2 : 1);
        }

        @Override
        protected void onCancelled() {
            runHttpFileLoadTasks(this, 2);
        }
    }

    private class HttpImageTask extends AsyncTask<Void, Void, Boolean> {

        private CacheImage cacheImage = null;
        private RandomAccessFile fileOutputStream = null;
        private int imageSize;
        private long lastProgressTime;
        private boolean canRetry = true;
        private URLConnection httpConnection = null;

        public HttpImageTask(CacheImage cacheImage, int size) {
            this.cacheImage = cacheImage;
            imageSize = size;
        }

        private void reportProgress(final float progress) {
            long currentTime = System.currentTimeMillis();
            if (progress == 1 || lastProgressTime == 0 || lastProgressTime < currentTime - 500) {
                lastProgressTime = currentTime;
                Utilities.stageQueue.postRunnable(new Runnable() {
                    @Override
                    public void run() {
                        fileProgresses.put(cacheImage.url, progress);
                        AndroidUtilities.runOnUIThread(new Runnable() {
                            @Override
                            public void run() {
                                NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, cacheImage.url, progress);
                            }
                        });
                    }
                });
            }
        }

        protected Boolean doInBackground(Void... voids) {
            InputStream httpConnectionStream = null;
            boolean done = false;

            if (!isCancelled()) {
                try {
                    URL downloadUrl = new URL(cacheImage.httpUrl);
                    httpConnection = downloadUrl.openConnection();
                    httpConnection.setConnectTimeout(5000);
                    httpConnection.setReadTimeout(5000);
                    if (!isCancelled()) {
                        httpConnection.connect();
                        httpConnectionStream = httpConnection.getInputStream();

                        fileOutputStream = new RandomAccessFile(cacheImage.tempFilePath, "rws");
                    }
                } catch (Throwable e) {
                    FileLog.e("tmessages", e);
                }
            }

            if (!isCancelled()) {
                try {
                    if (httpConnection != null && httpConnection instanceof HttpURLConnection) {
                        int code = ((HttpURLConnection) httpConnection).getResponseCode();
                        if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
                            canRetry = false;
                        }
                    }
                } catch (Exception e) {
                    FileLog.e("tmessages", e);
                }

                try {
                    byte[] data = new byte[1024 * 2];
                    int totalLoaded = 0;
                    while (true) {
                        if (isCancelled()) {
                            break;
                        }
                        try {
                            int readed = httpConnectionStream.read(data);
                            if (readed > 0) {
                                totalLoaded += readed;
                                fileOutputStream.write(data, 0, readed);
                                if (imageSize != 0) {
                                    reportProgress(totalLoaded / (float) imageSize);
                                }
                            } else if (readed == -1) {
                                done = true;
                                if (imageSize != 0) {
                                    reportProgress(1.0f);
                                }
                                break;
                            } else {
                                break;
                            }
                        } catch (Exception e) {
                            FileLog.e("tmessages", e);
                            break;
                        }
                    }
                } catch (Throwable e) {
                    FileLog.e("tmessages", e);
                }
            }

            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                    fileOutputStream = null;
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            try {
                if (httpConnectionStream != null) {
                    httpConnectionStream.close();
                }
                httpConnectionStream = null;
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }

            if (done) {
                if (cacheImage.tempFilePath != null) {
                    if (!cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath)) {
                        cacheImage.finalFilePath = cacheImage.tempFilePath;
                    }
                }
            }

            return done;
        }

        @Override
        protected void onPostExecute(final Boolean result) {
            if (result || !canRetry) {
                fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, FileLoader.MEDIA_DIR_IMAGE);
            } else {
                httpFileLoadError(cacheImage.url);
            }
            Utilities.stageQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    fileProgresses.remove(cacheImage.url);
                    AndroidUtilities.runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            if (result) {
                                NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, cacheImage.url);
                            } else {
                                NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 2);
                            }
                        }
                    });
                }
            });
            imageLoadQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    runHttpTasks(true);
                }
            });
        }

        @Override
        protected void onCancelled() {
            imageLoadQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    runHttpTasks(true);
                }
            });
            Utilities.stageQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    fileProgresses.remove(cacheImage.url);
                    AndroidUtilities.runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 1);
                        }
                    });
                }
            });
        }
    }

    private class ThumbGenerateTask implements Runnable {

        private File originalPath;
        private int mediaType;
        private TLRPC.FileLocation thumbLocation;
        private String filter;

        public ThumbGenerateTask(int type, File path, TLRPC.FileLocation location, String f) {
            mediaType = type;
            originalPath = path;
            thumbLocation = location;
            filter = f;
        }

        private void removeTask() {
            if (thumbLocation == null) {
                return;
            }
            final String name = FileLoader.getAttachFileName(thumbLocation);
            imageLoadQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    thumbGenerateTasks.remove(name);
                }
            });
        }

        @Override
        public void run() {
            try {
                if (thumbLocation == null) {
                    removeTask();
                    return;
                }
                final String key = thumbLocation.volume_id + "_" + thumbLocation.local_id;
                File thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + key + ".jpg");
                if (thumbFile.exists() || !originalPath.exists()) {
                    removeTask();
                    return;
                }
                int size = Math.min(180, Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / 4);
                Bitmap originalBitmap = null;
                if (mediaType == FileLoader.MEDIA_DIR_IMAGE) {
                    originalBitmap = ImageLoader.loadBitmap(originalPath.toString(), null, size, size, false);
                } else if (mediaType == FileLoader.MEDIA_DIR_VIDEO) {
                    originalBitmap = ThumbnailUtils.createVideoThumbnail(originalPath.toString(), MediaStore.Video.Thumbnails.MINI_KIND);
                } else if (mediaType == FileLoader.MEDIA_DIR_DOCUMENT) {
                    String path = originalPath.toString().toLowerCase();
                    if (!path.endsWith(".jpg") && !path.endsWith(".jpeg") && !path.endsWith(".png") && !path.endsWith(".gif")) {
                        removeTask();
                        return;
                    }
                    originalBitmap = ImageLoader.loadBitmap(path, null, size, size, false);
                }
                if (originalBitmap == null) {
                    removeTask();
                    return;
                }

                int w = originalBitmap.getWidth();
                int h = originalBitmap.getHeight();
                if (w == 0 || h == 0) {
                    removeTask();
                    return;
                }
                float scaleFactor = Math.min((float) w / size, (float) h / size);
                Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, (int) (w / scaleFactor), (int) (h / scaleFactor), true);
                if (scaledBitmap != originalBitmap) {
                    originalBitmap.recycle();
                    callGC();
                }
                originalBitmap = scaledBitmap;
                FileOutputStream stream = new FileOutputStream(thumbFile);
                originalBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream);
                try {
                    stream.close();
                } catch (Exception e) {
                    FileLog.e("tmessages", e);
                }
                final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap);
                AndroidUtilities.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        removeTask();

                        String kf = key;
                        if (filter != null) {
                            kf += "@" + filter;
                        }
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageThumbGenerated, bitmapDrawable, kf);
                        /*BitmapDrawable old = memCache.get(kf);
                        if (old != null) {
                            Bitmap image = old.getBitmap();
                            if (runtimeHack != null) {
                                runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight());
                            }
                            if (!image.isRecycled()) {
                                image.recycle();
                            }
                        }*/
                        memCache.put(kf, bitmapDrawable);
                    }
                });
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
                removeTask();
            }
        }
    }

    private class CacheOutTask implements Runnable {
        private Thread runningThread;
        private final Object sync = new Object();

        private CacheImage cacheImage;
        private boolean isCancelled;

        public CacheOutTask(CacheImage image) {
            cacheImage = image;
        }

        @Override
        public void run() {
            synchronized (sync) {
                runningThread = Thread.currentThread();
                Thread.interrupted();
                if (isCancelled) {
                    return;
                }
            }

            Long mediaId = null;
            Bitmap image = null;
            File cacheFileFinal = cacheImage.finalFilePath;
            boolean canDeleteFile = true;
            boolean isWebp = false;

            if (cacheFileFinal.toString().endsWith("webp")) {
                isWebp = true;
            }

            if (cacheImage.thumb) {

                int blurType = 0;
                if (cacheImage.filter != null) {
                    if (cacheImage.filter.contains("b2")) {
                        blurType = 3;
                    } else if (cacheImage.filter.contains("b1")) {
                        blurType = 2;
                    } else if (cacheImage.filter.contains("b")) {
                        blurType = 1;
                    }
                }

                try {
                    lastCacheOutTime = System.currentTimeMillis();
                    synchronized (sync) {
                        if (isCancelled) {
                            return;
                        }
                    }

                    if (image == null) {
                        if (isWebp) {
                            RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
                            ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
                            image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
                            file.close();
                        } else {
                            FileInputStream is = new FileInputStream(cacheFileFinal);
                            image = BitmapFactory.decodeStream(is, null, null);
                            is.close();
                        }
                    }
                    if (image == null) {
                        if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
                            cacheFileFinal.delete();
                        }
                    } else {
                        if (image != null) {
                            if (blurType == 1) {
                                Utilities.blurBitmap(image, 3);
                            } else if (blurType == 2) {
                                Utilities.blurBitmap(image, 1);
                            } else if (blurType == 3) {
                                Utilities.blurBitmap(image, 7);
                                Utilities.blurBitmap(image, 7);
                                Utilities.blurBitmap(image, 7);
                            }
                        }
                        if (runtimeHack != null) {
                            runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
                        }
                    }
                } catch (Throwable e) {
                    FileLog.e("tmessages", e);
                }
            } else {
                try {
                    if (cacheImage.httpUrl != null) {
                        if (cacheImage.httpUrl.startsWith("thumb://")) {
                            int idx = cacheImage.httpUrl.indexOf(":", 8);
                            if (idx >= 0) {
                                mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx));
                            }
                            canDeleteFile = false;
                        } else if (!cacheImage.httpUrl.startsWith("http")) {
                            canDeleteFile = false;
                        }
                    }

                    int delay = 20;
                    if (runtimeHack != null) {
                        delay = 60;
                    }
                    if (mediaId != null) {
                        delay = 0;
                    }
                    if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay && Build.VERSION.SDK_INT < 21) {
                        Thread.sleep(delay);
                    }
                    lastCacheOutTime = System.currentTimeMillis();
                    synchronized (sync) {
                        if (isCancelled) {
                            return;
                        }
                    }

                    BitmapFactory.Options opts = new BitmapFactory.Options();

                    float w_filter = 0;
                    float h_filter = 0;
                    boolean blur = false;
                    if (cacheImage.filter != null) {
                        String args[] = cacheImage.filter.split("_");
                        w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density;
                        h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density;
                        if (args.length > 2) {
                            blur = true;
                        }
                        opts.inJustDecodeBounds = true;

                        if (mediaId != null) {
                            MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts);
                        } else {
                            FileInputStream is = new FileInputStream(cacheFileFinal);
                            image = BitmapFactory.decodeStream(is, null, opts);
                            is.close();
                        }

                        float photoW = opts.outWidth;
                        float photoH = opts.outHeight;
                        float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter);
                        if (scaleFactor < 1) {
                            scaleFactor = 1;
                        }
                        opts.inJustDecodeBounds = false;
                        opts.inSampleSize = (int)scaleFactor;
                    }
                    synchronized (sync) {
                        if (isCancelled) {
                            return;
                        }
                    }

                    if (cacheImage.filter == null || blur || cacheImage.httpUrl != null) {
                        opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
                    } else {
                        opts.inPreferredConfig = Bitmap.Config.RGB_565;
                    }
                    opts.inDither = false;
                    if (mediaId != null) {
                        image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, null);
                    }
                    if (image == null) {
                        if (isWebp) {
                            RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r");
                            ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length());
                            image = Utilities.loadWebpImage(buffer, buffer.limit(), null);
                            file.close();
                        } else {
                            FileInputStream is = new FileInputStream(cacheFileFinal);
                            image = BitmapFactory.decodeStream(is, null, opts);
                            is.close();
                        }
                    }
                    if (image == null) {
                        if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) {
                            cacheFileFinal.delete();
                        }
                    } else {
                        if (cacheImage.filter != null) {
                            float bitmapW = image.getWidth();
                            float bitmapH = image.getHeight();
                            if (bitmapW != w_filter && bitmapW > w_filter) {
                                float scaleFactor = bitmapW / w_filter;
                                Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true);
                                if (image != scaledBitmap) {
                                    image.recycle();
                                    callGC();
                                    image = scaledBitmap;
                                }
                            }
                            if (image != null && blur && bitmapH < 100 && bitmapW < 100) {
                                Utilities.blurBitmap(image, 3);
                            }
                        }
                        if (runtimeHack != null) {
                            runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
                        }
                    }
                } catch (Throwable e) {
                    //don't promt
                }
            }
            Thread.interrupted();
            onPostExecute(image != null ? new BitmapDrawable(image) : null);
        }

        private void onPostExecute(final BitmapDrawable bitmapDrawable) {
            AndroidUtilities.runOnUIThread(new Runnable() {
                @Override
                public void run() {
                    BitmapDrawable toSet = null;
                    if (bitmapDrawable != null) {
                        toSet = memCache.get(cacheImage.key);
                        if (toSet == null) {
                            memCache.put(cacheImage.key, bitmapDrawable);
                            toSet = bitmapDrawable;
                        } else {
                            Bitmap image = bitmapDrawable.getBitmap();
                            if (runtimeHack != null) {
                                runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight());
                            }
                            image.recycle();
                            callGC();
                        }
                    }
                    final BitmapDrawable toSetFinal = toSet;
                    imageLoadQueue.postRunnable(new Runnable() {
                        @Override
                        public void run() {
                            cacheImage.setImageAndClear(toSetFinal);
                        }
                    });
                }
            });
        }

        public void cancel() {
            synchronized (sync) {
                try {
                    isCancelled = true;
                    if (runningThread != null) {
                        runningThread.interrupt();
                    }
                } catch (Exception e) {
                    //don't promt
                }
            }
        }
    }

    public class VMRuntimeHack {
        private Object runtime = null;
        private Method trackAllocation = null;
        private Method trackFree = null;

        public boolean trackAlloc(long size) {
            if (runtime == null) {
                return false;
            }
            try {
                Object res = trackAllocation.invoke(runtime, size);
                return (res instanceof Boolean) ? (Boolean)res : true;
            } catch (Exception e) {
                return false;
            }
        }

        public boolean trackFree(long size) {
            if (runtime == null) {
                return false;
            }
            try {
                Object res = trackFree.invoke(runtime, size);
                return (res instanceof Boolean) ? (Boolean)res : true;
            } catch (Exception e) {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        public VMRuntimeHack() {
            try {
                Class cl = Class.forName("dalvik.system.VMRuntime");
                Method getRt = cl.getMethod("getRuntime", new Class[0]);
                Object[] objects = new Object[0];
                runtime = getRt.invoke(null, objects);
                trackAllocation = cl.getMethod("trackExternalAllocation", new Class[] {long.class});
                trackFree = cl.getMethod("trackExternalFree", new Class[] {long.class});
            } catch (Exception e) {
                FileLog.e("tmessages", e);
                runtime = null;
                trackAllocation = null;
                trackFree = null;
            }
        }
    }

    private class CacheImage {
        protected String key;
        protected String url;
        protected String filter;
        protected TLObject location;

        protected File finalFilePath;
        protected File tempFilePath;
        protected boolean thumb;

        protected String httpUrl;
        protected HttpImageTask httpTask;
        protected CacheOutTask cacheTask;

        protected ArrayList<ImageReceiver> imageReceiverArray = new ArrayList<>();

        public void addImageReceiver(ImageReceiver imageReceiver) {
            boolean exist = false;
            for (ImageReceiver v : imageReceiverArray) {
                if (v == imageReceiver) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                imageReceiverArray.add(imageReceiver);
                imageLoadingByTag.put(imageReceiver.getTag(thumb), this);
            }
        }

        public void removeImageReceiver(ImageReceiver imageReceiver) {
            for (int a = 0; a < imageReceiverArray.size(); a++) {
                ImageReceiver obj = imageReceiverArray.get(a);
                if (obj == null || obj == imageReceiver) {
                    imageReceiverArray.remove(a);
                    if (obj != null) {
                        imageLoadingByTag.remove(obj.getTag(thumb));
                    }
                    a--;
                }
            }
            if (imageReceiverArray.size() == 0) {
                for (ImageReceiver receiver : imageReceiverArray) {
                    imageLoadingByTag.remove(receiver.getTag(thumb));
                }
                imageReceiverArray.clear();
                if (location != null) {
                    if (location instanceof TLRPC.FileLocation) {
                        FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location);
                    } else if (location instanceof TLRPC.Document) {
                        FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location);
                    }
                }
                if (cacheTask != null) {
                    if (thumb) {
                        cacheThumbOutQueue.cancelRunnable(cacheTask);
                    } else {
                        cacheOutQueue.cancelRunnable(cacheTask);
                    }
                    cacheTask.cancel();
                    cacheTask = null;
                }
                if (httpTask != null) {
                    httpTasks.remove(httpTask);
                    httpTask.cancel(true);
                    httpTask = null;
                }
                if (url != null) {
                    imageLoadingByUrl.remove(url);
                }
                if (key != null) {
                    imageLoadingByKeys.remove(key);
                }
            }
        }

        public void setImageAndClear(final BitmapDrawable image) {
            if (image != null) {
                final ArrayList<ImageReceiver> finalImageReceiverArray = new ArrayList<>(imageReceiverArray);
                AndroidUtilities.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        for (ImageReceiver imgView : finalImageReceiverArray) {
                            imgView.setImageBitmapByKey(image, key, thumb);
                        }
                    }
                });
            }
            for (ImageReceiver imageReceiver : imageReceiverArray) {
                imageLoadingByTag.remove(imageReceiver.getTag(thumb));
            }
            imageReceiverArray.clear();
            if (url != null) {
                imageLoadingByUrl.remove(url);
            }
            if (key != null) {
                imageLoadingByKeys.remove(key);
            }
        }
    }

    private static volatile ImageLoader Instance = null;
    public static ImageLoader getInstance() {
        ImageLoader localInstance = Instance;
        if (localInstance == null) {
            synchronized (ImageLoader.class) {
                localInstance = Instance;
                if (localInstance == null) {
                    Instance = localInstance = new ImageLoader();
                }
            }
        }
        return localInstance;
    }

    public ImageLoader() {
        int cacheSize = Math.min(15, ((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() / 7) * 1024 * 1024;

        if (Build.VERSION.SDK_INT < 11) {
            runtimeHack = new VMRuntimeHack();
            cacheSize = 1024 * 1024 * 3;
        }
        memCache = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, BitmapDrawable bitmap) {
                Bitmap b = bitmap.getBitmap();
                if(Build.VERSION.SDK_INT < 12) {
                    return b.getRowBytes() * b.getHeight();
                } else {
                    return b.getByteCount();
                }
            }
            @Override
            protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) {
                if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) {
                    return;
                }
                final Integer count = bitmapUseCounts.get(key);
                if (count == null || count == 0) {
                    Bitmap b = oldBitmap.getBitmap();
                    if (runtimeHack != null) {
                        runtimeHack.trackAlloc(b.getRowBytes() * b.getHeight());
                    }
                    if (!b.isRecycled()) {
                        b.recycle();
                    }
                }
            }
        };

        FileLoader.getInstance().setDelegate(new FileLoader.FileLoaderDelegate() {
            @Override
            public void fileUploadProgressChanged(final String location, final float progress, final boolean isEncrypted) {
                fileProgresses.put(location, progress);
                long currentTime = System.currentTimeMillis();
                if (lastProgressUpdateTime == 0 || lastProgressUpdateTime < currentTime - 500) {
                    lastProgressUpdateTime = currentTime;

                    AndroidUtilities.runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileUploadProgressChanged, location, progress, isEncrypted);
                        }
                    });
                }
            }

            @Override
            public void fileDidUploaded(final String location, final TLRPC.InputFile inputFile, final TLRPC.InputEncryptedFile inputEncryptedFile) {
                Utilities.stageQueue.postRunnable(new Runnable() {
                    @Override
                    public void run() {
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidUpload, location, inputFile, inputEncryptedFile);
                        fileProgresses.remove(location);
                    }
                });
            }

            @Override
            public void fileDidFailedUpload(final String location, final boolean isEncrypted) {
                Utilities.stageQueue.postRunnable(new Runnable() {
                    @Override
                    public void run() {
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailUpload, location, isEncrypted);
                        fileProgresses.remove(location);
                    }
                });
            }

            @Override
            public void fileDidLoaded(final String location, final File finalFile, final int type) {
                fileProgresses.remove(location);
                AndroidUtilities.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        if (location != null) {
                            if (MediaController.getInstance().canSaveToGallery() && telegramPath != null && finalFile != null && finalFile.exists() && (location.endsWith(".mp4") || location.endsWith(".jpg"))) {
                                if (finalFile.toString().startsWith(telegramPath.toString())) {
                                    Utilities.addMediaToGallery(finalFile.toString());
                                }
                            }
                        }
                        ImageLoader.this.fileDidLoaded(location, finalFile, type);
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, location);
                    }
                });
            }

            @Override
            public void fileDidFailedLoad(final String location, final int canceled) {
                fileProgresses.remove(location);
                AndroidUtilities.runOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        ImageLoader.this.fileDidFailedLoad(location, canceled);
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, canceled);
                    }
                });
            }

            @Override
            public void fileLoadProgressChanged(final String location, final float progress) {
                fileProgresses.put(location, progress);
                long currentTime = System.currentTimeMillis();
                if (lastProgressUpdateTime == 0 || lastProgressUpdateTime < currentTime - 500) {
                    lastProgressUpdateTime = currentTime;
                    AndroidUtilities.runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, location, progress);
                        }
                    });
                }
            }
        });

        BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context arg0, Intent intent) {
                FileLog.e("tmessages", "file system changed");
                Runnable r = new Runnable() {
                    public void run() {
                        FileLoader.getInstance().setMediaDirs(createMediaPaths());
                    }
                };
                if (Intent.ACTION_MEDIA_UNMOUNTED.equals(intent.getAction())) {
                    AndroidUtilities.runOnUIThread(r, 1000);
                } else {
                    r.run();
                }
            }
        };

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
        filter.addAction(Intent.ACTION_MEDIA_CHECKING);
        filter.addAction(Intent.ACTION_MEDIA_EJECT);
        filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
        filter.addAction(Intent.ACTION_MEDIA_NOFS);
        filter.addAction(Intent.ACTION_MEDIA_REMOVED);
        filter.addAction(Intent.ACTION_MEDIA_SHARED);
        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE);
        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        filter.addDataScheme("file");
        ApplicationLoader.applicationContext.registerReceiver(receiver, filter);

        FileLoader.getInstance().setMediaDirs(createMediaPaths());
    }

    private HashMap<Integer, File> createMediaPaths() {
        HashMap<Integer, File> mediaDirs = new HashMap<>();
        File cachePath = AndroidUtilities.getCacheDir();
        if (!cachePath.isDirectory()) {
            try {
                cachePath.mkdirs();
            } catch (Exception e) {
                FileLog.e("tmessages", e);
            }
        }
        try {
            new File(cachePath, ".nomedia").createNewFile();
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }

        mediaDirs.put(FileLoader.MEDIA_DIR_CACHE, cachePath);
        FileLog.e("tmessages", "cache path = " + cachePath);

        try {
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                telegramPath = new File(Environment.getExternalStorageDirectory(), "Telegram");
                telegramPath.mkdirs();

                boolean canRename = false;

                try {
                    for (int a = 0; a < 5; a++) {
                        File srcFile = new File(cachePath, "temp.file");
                        srcFile.createNewFile();
                        File dstFile = new File(telegramPath, "temp.file");
                        canRename = srcFile.renameTo(dstFile);
                        srcFile.delete();
                        dstFile.delete();
                        if (canRename) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    FileLog.e("tmessages", e);
                }

                if (canRename) {
                    if (telegramPath.isDirectory()) {
                        try {
                            File imagePath = new File(telegramPath, "Telegram Images");
                            imagePath.mkdir();
                            if (imagePath.isDirectory()) {
                                mediaDirs.put(FileLoader.MEDIA_DIR_IMAGE, imagePath);
                                FileLog.e("tmessages", "image path = " + imagePath);
                            }
                        } catch (Exception e) {
                            FileLog.e("tmessages", e);
                        }

                        try {
                            File videoPath = new File(telegramPath, "Telegram Video");
                            videoPath.mkdir();
                            if (videoPath.isDirectory()) {
                                mediaDirs.put(FileLoader.MEDIA_DIR_VIDEO, videoPath);
                                FileLog.e("tmessages", "video path = " + videoPath);
                            }
                        } catch (Exception e) {
                            FileLog.e("tmessages", e);
                        }

                        try {
                            File audioPath = new File(telegramPath, "Telegram Audio");
                            audioPath.mkdir();
                            if (audioPath.isDirectory()) {
                                new File(audioPath, ".nomedia").createNewFile();
                                mediaDirs.put(FileLoader.MEDIA_DIR_AUDIO, audioPath);
                                FileLog.e("tmessages", "audio path = " + audioPath);
                            }
                        } catch (Exception e) {
                            FileLog.e("tmessages", e);
                        }

                        try {
                            File documentPath = new File(telegramPath, "Telegram Documents");
                            documentPath.mkdir();
                            if (documentPath.isDirectory()) {
                                new File(documentPath, ".nomedia").createNewFile();
                                mediaDirs.put(FileLoader.MEDIA_DIR_DOCUMENT, documentPath);
                                FileLog.e("tmessages", "documents path = " + documentPath);
                            }
                        } catch (Exception e) {
                            FileLog.e("tmessages", e);
                        }
                    }
                } else {
                    FileLog.e("tmessages", "this Android can't rename files");
                }
            }
            MediaController.getInstance().checkSaveToGalleryFiles();
        } catch (Exception e) {
            FileLog.e("tmessages", e);
        }

        return mediaDirs;
    }

    public Float getFileProgress(String location) {
        return fileProgresses.get(location);
    }

    private void performReplace(String oldKey, String newKey) {
        BitmapDrawable b = memCache.get(oldKey);
        if (b != null) {
            ignoreRemoval = oldKey;
            memCache.remove(oldKey);
            memCache.put(newKey, b);
            ignoreRemoval = null;
        }
        Integer val = bitmapUseCounts.get(oldKey);
        if (val != null) {
            bitmapUseCounts.put(newKey, val);
            bitmapUseCounts.remove(oldKey);
        }
    }

    public void incrementUseCount(String key) {
        Integer count = bitmapUseCounts.get(key);
        if (count == null) {
            bitmapUseCounts.put(key, 1);
        } else {
            bitmapUseCounts.put(key, count + 1);
        }
    }

    public void callGC() {
        if (Build.VERSION.SDK_INT > 13) {
            recycleQueue.postRunnable(new Runnable() {
                @Override
                public void run() {
                    System.gc();
                }
            });
        }
    }

    public boolean decrementUseCount(String key) {
        Integer count = bitmapUseCounts.get(key);
        if (count == null) {
            return true;
        }
        if (count == 1) {
            bitmapUseCounts.remove(key);
            return true;
        } else {
            bitmapUseCounts.put(key, count - 1);
        }
        return false;
    }

    public void removeImage(String key) {
        bitmapUseCounts.remove(key);
        memCache.remove(key);
    }

    public boolean isInCache(String key) {
        return memCache.get(key) != null;
    }

    public void clearMemory() {
        memCache.evictAll();
    }

    private void removeFromWaitingForThumb(Integer TAG) {
        String location = waitingForQualityThumbByTag.get(TAG);
        if (location != null) {
            ThumbGenerateInfo info = waitingForQualityThumb.get(location);
            if (info != null) {
                info.count--;
                if (info.count == 0) {
                    waitingForQualityThumb.remove(location);
                }
            }
            waitingForQualityThumbByTag.remove(TAG);
        }
    }

    public void cancelLoadingForImageReceiver(final ImageReceiver imageReceiver, final int type) {
        if (imageReceiver == null) {
            return;
        }
        imageLoadQueue.postRunnable(new Runnable() {
            @Override
            public void run() {
                int start = 0;
                int count = 2;
                if (type == 1) {
                    count = 1;
                } else if (type == 2) {
                    start = 1;
                }
                for (int a = start; a < count; a++) {
                    Integer TAG = imageReceiver.getTag(a == 0);
                    if (a == 0) {
                        removeFromWaitingForThumb(TAG);
                    }
                    if (TAG != null) {
                        CacheImage ei = imageLoadingByTag.get(TAG);
                        if (ei != null) {
                            ei.removeImageReceiver(imageReceiver);
                        }
                    }
                }
            }
        });
    }

    public BitmapDrawable getImageFromMemory(String key) {
        return memCache.get(key);
    }

    public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, String filter) {
        if (fileLocation == null && httpUrl == null) {
            return null;
        }
        String key = null;
        if (httpUrl != null) {
            key = Utilities.MD5(httpUrl);
        } else {
            if (fileLocation instanceof TLRPC.FileLocation) {
                TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation;
                key = location.volume_id + "_" + location.local_id;
            } else if (fileLocation instanceof TLRPC.Document) {
                TLRPC.Document location = (TLRPC.Document) fileLocation;
                key = location.dc_id + "_" + location.id;
            }
        }
        if (filter != null) {
            key += "@" + filter;
        }
        return memCache.get(key);
    }

    public void replaceImageInCache(final String oldKey, final String newKey) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                ArrayList<String> arr = memCache.getFilterKeys(oldKey);
                if (arr != null) {
                    for (String filter : arr) {
                        performReplace(oldKey + "@" + filter, newKey + "@" + filter);
                    }
                } else {
                    performReplace(oldKey, newKey);
                }
            }
        });
    }

    public void putImageToCache(BitmapDrawable bitmap, String key) {
        memCache.put(key, bitmap);
    }

    private void generateThumb(int mediaType, File originalPath, TLRPC.FileLocation thumbLocation, String filter) {
        if (mediaType != FileLoader.MEDIA_DIR_IMAGE && mediaType != FileLoader.MEDIA_DIR_VIDEO && mediaType != FileLoader.MEDIA_DIR_DOCUMENT || originalPath == null || thumbLocation == null) {
            return;
        }
        String name = FileLoader.getAttachFileName(thumbLocation);
        ThumbGenerateTask task = thumbGenerateTasks.get(name);
        if (task == null) {
            task = new ThumbGenerateTask(mediaType, originalPath, thumbLocation, filter);
            thumbGeneratingQueue.postRunnable(task);
        }
    }

    private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final TLObject imageLocation, final String httpLocation, final String filter, final int size, final boolean cacheOnly, final int thumb) {
        if (imageReceiver == null || url == null || key == null) {
            return;
        }
        Integer TAG = imageReceiver.getTag(thumb != 0);
        if (TAG == null) {
            imageReceiver.setTag(TAG = lastImageNum, thumb != 0);
            lastImageNum++;
            if (lastImageNum == Integer.MAX_VALUE) {
                lastImageNum = 0;
            }
        }

        final Integer finalTag = TAG;
        final boolean finalIsNeedsQualityThumb = imageReceiver.isNeedsQualityThumb();
        final MessageObject parentMessageObject = imageReceiver.getParentMessageObject();
        final boolean shouldGenerateQualityThumb = imageReceiver.isShouldGenerateQualityThumb();
        imageLoadQueue.postRunnable(new Runnable() {
            @Override
            public void run() {
                boolean added = false;
                if (thumb != 2) {
                    CacheImage alreadyLoadingUrl = imageLoadingByUrl.get(url);
                    CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key);
                    CacheImage alreadyLoadingImage = imageLoadingByTag.get(finalTag);
                    if (alreadyLoadingImage != null) {
                        if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) {
                            added = true;
                        } else {
                            alreadyLoadingImage.removeImageReceiver(imageReceiver);
                        }
                    }

                    if (!added && alreadyLoadingCache != null) {
                        alreadyLoadingCache.addImageReceiver(imageReceiver);
                        added = true;
                    }
                    if (!added && alreadyLoadingUrl != null) {
                        alreadyLoadingUrl.addImageReceiver(imageReceiver);
                        added = true;
                    }
                }

                if (!added) {
                    boolean onlyCache = false;
                    boolean isQuality = false;
                    File cacheFile = null;

                    if (httpLocation != null) {
                        if (!httpLocation.startsWith("http")) {
                            onlyCache = true;
                            if (httpLocation.startsWith("thumb://")) {
                                int idx = httpLocation.indexOf(":", 8);
                                if (idx >= 0) {
                                    cacheFile = new File(httpLocation.substring(idx + 1));
                                }
                            } else {
                                cacheFile = new File(httpLocation);
                            }
                        }
                    } else if (thumb != 0) {
                        if (finalIsNeedsQualityThumb) {
                            cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + url);
                            if (!cacheFile.exists()) {
                                cacheFile = null;
                            }
                        }

                        if (parentMessageObject != null) {
                            File attachPath = null;
                            if (parentMessageObject.messageOwner.attachPath != null && parentMessageObject.messageOwner.attachPath.length() > 0) {
                                attachPath = new File(parentMessageObject.messageOwner.attachPath);
                                if (!attachPath.exists()) {
                                    attachPath = null;
                                }
                            }
                            if (attachPath == null) {
                                attachPath = FileLoader.getPathToMessage(parentMessageObject.messageOwner);
                            }
                            if (finalIsNeedsQualityThumb && cacheFile == null) {
                                String location = parentMessageObject.getFileName();
                                ThumbGenerateInfo info = waitingForQualityThumb.get(location);
                                if (info == null) {
                                    info = new ThumbGenerateInfo();
                                    info.fileLocation = (TLRPC.TL_fileLocation) imageLocation;
                                    info.filter = filter;
                                    waitingForQualityThumb.put(location, info);
                                }
                                info.count++;
                                waitingForQualityThumbByTag.put(finalTag, location);
                            }
                            if (attachPath.exists() && shouldGenerateQualityThumb) {
                                generateThumb(parentMessageObject.getFileType(), attachPath, (TLRPC.TL_fileLocation) imageLocation, filter);
                            }
                        }
                    }

                    if (thumb != 2) {
                        if (cacheFile == null) {
                            if (cacheOnly || size == 0 || httpLocation != null) {
                                cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url);
                            } else {
                                cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url);
                            }
                        }

                        CacheImage img = new CacheImage();
                        img.thumb = thumb != 0;
                        img.key = key;
                        img.filter = filter;
                        img.httpUrl = httpLocation;
                        img.addImageReceiver(imageReceiver);
                        if (onlyCache || cacheFile.exists()) {
                            img.finalFilePath = cacheFile;
                            img.cacheTask = new CacheOutTask(img);
                            imageLoadingByKeys.put(key, img);
                            if (thumb != 0) {
                                cacheThumbOutQueue.postRunnable(img.cacheTask);
                            } else {
                                cacheOutQueue.postRunnable(img.cacheTask);
                            }
                        } else {
                            img.url = url;
                            img.location = imageLocation;
                            imageLoadingByUrl.put(url, img);
                            if (httpLocation == null) {
                                if (imageLocation instanceof TLRPC.FileLocation) {
                                    TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation;
                                    FileLoader.getInstance().loadFile(location, size, size == 0 || location.key != null || cacheOnly);
                                } else if (imageLocation instanceof TLRPC.Document) {
                                    FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, true);
                                }
                            } else {
                                String file = Utilities.MD5(httpLocation);
                                File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE);
                                img.tempFilePath = new File(cacheDir, file + "_temp.jpg");
                                img.finalFilePath = cacheFile;
                                img.httpTask = new HttpImageTask(img, size);
                                httpTasks.add(img.httpTask);
                                runHttpTasks(false);
                            }
                        }
                    }
                }
            }
        });
    }

    public void loadImageForImageReceiver(ImageReceiver imageReceiver) {
        if (imageReceiver == null) {
            return;
        }

        String key = imageReceiver.getKey();
        if (key != null) {
            BitmapDrawable bitmapDrawable = memCache.get(key);
            if (bitmapDrawable != null) {
                cancelLoadingForImageReceiver(imageReceiver, 0);
                if (!imageReceiver.isForcePreview()) {
                    imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false);
                    return;
                }
            }
        }
        boolean thumbSet = false;
        String thumbKey = imageReceiver.getThumbKey();
        if (thumbKey != null) {
            BitmapDrawable bitmapDrawable = memCache.get(thumbKey);
            if (bitmapDrawable != null) {
                imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true);
                cancelLoadingForImageReceiver(imageReceiver, 1);
                thumbSet = true;
            }
        }

        TLRPC.FileLocation thumbLocation = imageReceiver.getThumbLocation();
        TLObject imageLocation = imageReceiver.getImageLocation();
        String httpLocation = imageReceiver.getHttpImageLocation();

        boolean saveImageToCache = false;

        String url = null;
        String thumbUrl = null;
        key = null;
        thumbKey = null;
        String ext = null;
        if (httpLocation != null) {
            key = Utilities.MD5(httpLocation);
            url = key + "." + getHttpUrlExtension(httpLocation);
        } else if (imageLocation != null) {
            if (imageLocation instanceof TLRPC.FileLocation) {
                TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation;
                key = location.volume_id + "_" + location.local_id;
                ext = "." + (location.ext != null ? location.ext : "jpg");
                url = key + ext;
                if (location.ext != null || location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) {
                    saveImageToCache = true;
                }
            } else if (imageLocation instanceof TLRPC.Document) {
                TLRPC.Document location = (TLRPC.Document) imageLocation;
                if (location.id == 0 || location.dc_id == 0) {
                    return;
                }
                key = location.dc_id + "_" + location.id;
                ext = ".webp";
                url = key + ext;
                if (thumbKey != null) {
                    thumbUrl = thumbKey + ext;
                }
                saveImageToCache = true;
            }
            if (imageLocation == thumbLocation) {
                imageLocation = null;
                key = null;
                url = null;
            }
        }

        if (thumbLocation != null) {
            thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id;
            if (ext != null) {
                thumbUrl = thumbKey + ext;
            } else {
                thumbUrl = thumbKey + "." + (thumbLocation.ext != null ? thumbLocation.ext : "jpg");
            }
        }

        String filter = imageReceiver.getFilter();
        String thumbFilter = imageReceiver.getThumbFilter();
        if (key != null && filter != null) {
            key += "@" + filter;
        }
        if (thumbKey != null && thumbFilter != null) {
            thumbKey += "@" + thumbFilter;
        }

        if (httpLocation != null) {
            createLoadOperationForImageReceiver(imageReceiver, key, url, null, httpLocation, filter, 0, true, 0);
        } else {
            createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, thumbLocation, null, thumbFilter, 0, true, thumbSet ? 2 : 1);
            createLoadOperationForImageReceiver(imageReceiver, key, url, imageLocation, null, filter, imageReceiver.getSize(), saveImageToCache || imageReceiver.getCacheOnly(), 0);
        }
    }

    private void httpFileLoadError(final String location) {
        imageLoadQueue.postRunnable(new Runnable() {
            @Override
            public void run() {
                CacheImage img = imageLoadingByUrl.get(location);
                if (img == null) {
                    return;
                }
                HttpImageTask oldTask = img.httpTask;
                img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize);
                httpTasks.add(img.httpTask);
                runHttpTasks(false);
            }
        });
    }

    private void fileDidLoaded(final String location, final File finalFile, final int type) {
        imageLoadQueue.postRunnable(new Runnable() {
            @Override
            public void run() {
                ThumbGenerateInfo info = waitingForQualityThumb.get(location);
                if (info != null) {
                    generateThumb(type, finalFile, info.fileLocation, info.filter);
                    waitingForQualityThumb.remove(location);
                }
                CacheImage img = imageLoadingByUrl.get(location);
                if (img == null) {
                    return;
                }
                imageLoadingByUrl.remove(location);
                CacheOutTask task = null;
                for (ImageReceiver imageReceiver : img.imageReceiverArray) {
                    CacheImage cacheImage = imageLoadingByKeys.get(img.key);
                    if (cacheImage == null) {
                        cacheImage = new CacheImage();
                        cacheImage.finalFilePath = finalFile;
                        cacheImage.key = img.key;
                        cacheImage.httpUrl = img.httpUrl;
                        cacheImage.thumb = img.thumb;
                        cacheImage.cacheTask = task = new CacheOutTask(cacheImage);
                        cacheImage.filter = img.filter;
                        imageLoadingByKeys.put(cacheImage.key, cacheImage);
                    }
                    cacheImage.addImageReceiver(imageReceiver);
                }
                if (task != null) {
                    if (img.thumb) {
                        cacheThumbOutQueue.postRunnable(task);
                    } else {
                        cacheOutQueue.postRunnable(task);
                    }
                }
            }
        });
    }

    private void fileDidFailedLoad(final String location, int canceled) {
        if (canceled == 1) {
            return;
        }
        imageLoadQueue.postRunnable(new Runnable() {
            @Override
            public void run() {
                CacheImage img = imageLoadingByUrl.get(location);
                if (img != null) {
                    img.setImageAndClear(null);
                }
            }
        });
    }

    private void runHttpTasks(boolean complete) {
        if (complete) {
            currentHttpTasksCount--;
        }
        while (currentHttpTasksCount < 1 && !httpTasks.isEmpty()) {
            HttpImageTask task = httpTasks.poll();
            if (android.os.Build.VERSION.SDK_INT >= 11) {
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
            } else {
                task.execute(null, null, null);
            }
            currentHttpTasksCount++;
        }
    }

    public void loadHttpFile(String url, String extension) {
        if (url == null || url.length() == 0 || httpFileLoadTasksByKeys.containsKey(url)) {
            return;
        }
        String ext = extension;
        if (ext == null) {
            int idx = url.lastIndexOf(".");
            if (idx != -1) {
                ext = url.substring(idx + 1);
            }
            if (ext == null || ext.length() == 0 || ext.length() > 4) {
                ext = "jpg";
            }
        }
        File file = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.MD5(url) + "_temp." + ext);
        file.delete();

        HttpFileTask task = new HttpFileTask(url, file, ext);
        httpFileLoadTasks.add(task);
        httpFileLoadTasksByKeys.put(url, task);
        runHttpFileLoadTasks(null, 0);
    }

    public void cancelLoadHttpFile(String url) {
        HttpFileTask task = httpFileLoadTasksByKeys.get(url);
        if (task != null) {
            task.cancel(true);
            httpFileLoadTasksByKeys.remove(url);
            httpFileLoadTasks.remove(task);
        }
        Runnable runnable = retryHttpsTasks.get(url);
        if (runnable != null) {
            AndroidUtilities.cancelRunOnUIThread(runnable);
        }
        runHttpFileLoadTasks(null, 0);
    }

    private void runHttpFileLoadTasks(final HttpFileTask oldTask, final int reason) {
        AndroidUtilities.runOnUIThread(new Runnable() {
            @Override
            public void run() {
                if (oldTask != null) {
                    currentHttpFileLoadTasksCount--;
                }
                if (oldTask != null) {
                    if (reason == 1) {
                        if (oldTask.canRetry) {
                            final HttpFileTask newTask = new HttpFileTask(oldTask.url, oldTask.tempFile, oldTask.ext);
                            Runnable runnable = new Runnable() {
                                @Override
                                public void run() {
                                    httpFileLoadTasks.add(newTask);
                                    runHttpFileLoadTasks(null, 0);
                                }
                            };
                            retryHttpsTasks.put(oldTask.url, runnable);
                            AndroidUtilities.runOnUIThread(runnable, 1000);
                        } else {
                            NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidFailedLoad, oldTask.url);
                        }
                    } else if (reason == 2) {
                        httpFileLoadTasksByKeys.remove(oldTask.url);
                        File file = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.MD5(oldTask.url) + "." + oldTask.ext);
                        String result = oldTask.tempFile.renameTo(file) ? file.toString() : oldTask.tempFile.toString();
                        NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidLoaded, oldTask.url, result);
                    }
                }
                while (currentHttpFileLoadTasksCount < 2 && !httpFileLoadTasks.isEmpty()) {
                    HttpFileTask task = httpFileLoadTasks.poll();
                    if (android.os.Build.VERSION.SDK_INT >= 11) {
                        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
                    } else {
                        task.execute(null, null, null);
                    }
                    currentHttpFileLoadTasksCount++;
                }
            }
        });
    }

    public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) {
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        FileDescriptor fileDescriptor = null;
        ParcelFileDescriptor parcelFD = null;

        if (path == null && uri != null && uri.getScheme() != null) {
            String imageFilePath = null;
            if (uri.getScheme().contains("file")) {
                path = uri.getPath();
            } else {
                try {
                    path = Utilities.getPath(uri);
                } catch (Throwable e) {
                    FileLog.e("tmessages", e);
                }
            }
        }

        if (path != null) {
            BitmapFactory.decodeFile(path, bmOptions);
        } else if (uri != null) {
            boolean error = false;
            try {
                parcelFD = ApplicationLoader.applicationContext.getContentResolver().openFileDescriptor(uri, "r");
                fileDescriptor = parcelFD.getFileDescriptor();
                BitmapFactory.decodeFileDescriptor(fileDescriptor, null, bmOptions);
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
                try {
                    if (parcelFD != null) {
                        parcelFD.close();
                    }
                } catch (Throwable e2) {
                    FileLog.e("tmessages", e2);
                }
                return null;
            }
        }
        float photoW = bmOptions.outWidth;
        float photoH = bmOptions.outHeight;
        float scaleFactor = useMaxScale ? Math.max(photoW / maxWidth, photoH / maxHeight) : Math.min(photoW / maxWidth, photoH / maxHeight);
        if (scaleFactor < 1) {
            scaleFactor = 1;
        }
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = (int)scaleFactor;

        String exifPath = null;
        if (path != null) {
            exifPath = path;
        } else if (uri != null) {
            exifPath = Utilities.getPath(uri);
        }

        Matrix matrix = null;

        if (exifPath != null) {
            ExifInterface exif;
            try {
                exif = new ExifInterface(exifPath);
                int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
                matrix = new Matrix();
                switch (orientation) {
                    case ExifInterface.ORIENTATION_ROTATE_90:
                        matrix.postRotate(90);
                        break;
                    case ExifInterface.ORIENTATION_ROTATE_180:
                        matrix.postRotate(180);
                        break;
                    case ExifInterface.ORIENTATION_ROTATE_270:
                        matrix.postRotate(270);
                        break;
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            }
        }

        Bitmap b = null;
        if (path != null) {
            try {
                b = BitmapFactory.decodeFile(path, bmOptions);
                if (b != null) {
                    b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true);
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
                ImageLoader.getInstance().clearMemory();
                try {
                    if (b == null) {
                        b = BitmapFactory.decodeFile(path, bmOptions);
                    }
                    if (b != null) {
                        b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true);
                    }
                } catch (Throwable e2) {
                    FileLog.e("tmessages", e2);
                }
            }
        } else if (uri != null) {
            try {
                b = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, bmOptions);
                if (b != null) {
                    b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true);
                }
            } catch (Throwable e) {
                FileLog.e("tmessages", e);
            } finally {
                try {
                    if (parcelFD != null) {
                        parcelFD.close();
                    }
                } catch (Throwable e) {
                    FileLog.e("tmessages", e);
                }
            }
        }

        return b;
    }

    private static TLRPC.PhotoSize scaleAndSaveImageInternal(Bitmap bitmap, int w, int h, float photoW, float photoH, float scaleFactor, int quality, boolean cache, boolean scaleAnyway) throws Exception {
        Bitmap scaledBitmap = null;
        if (scaleFactor > 1 || scaleAnyway) {
            scaledBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
        } else {
            scaledBitmap = bitmap;
        }

        TLRPC.TL_fileLocation location = new TLRPC.TL_fileLocation();
        location.volume_id = Integer.MIN_VALUE;
        location.dc_id = Integer.MIN_VALUE;
        location.local_id = UserConfig.lastLocalId;
        UserConfig.lastLocalId--;
        TLRPC.PhotoSize size = new TLRPC.TL_photoSize();
        size.location = location;
        size.w = scaledBitmap.getWidth();
        size.h = scaledBitmap.getHeight();
        if (size.w <= 100 && size.h <= 100) {
            size.type = "s";
        } else if (size.w <= 320 && size.h <= 320) {
            size.type = "m";
        } else if (size.w <= 800 && size.h <= 800) {
            size.type = "x";
        } else if (size.w <= 1280 && size.h <= 1280) {
            size.type = "y";
        } else {
            size.type = "w";
        }

        String fileName = location.volume_id + "_" + location.local_id + ".jpg";
        final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
        FileOutputStream stream = new FileOutputStream(cacheFile);
        scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
        if (cache) {
            ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
            scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream2);
            size.bytes = stream2.toByteArray();
            size.size = size.bytes.length;
            stream2.close();
        } else {
            size.size = (int)stream.getChannel().size();
        }
        stream.close();
        if (scaledBitmap != bitmap) {
            scaledBitmap.recycle();
        }

        return size;
    }

    public static TLRPC.PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, int quality, boolean cache) {
        return scaleAndSaveImage(bitmap, maxWidth, maxHeight, quality, cache, 0, 0);
    }

    public static TLRPC.PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, float maxHeight, int quality, boolean cache, int minWidth, int minHeight) {
        if (bitmap == null) {
            return null;
        }
        float photoW = bitmap.getWidth();
        float photoH = bitmap.getHeight();
        if (photoW == 0 || photoH == 0) {
            return null;
        }
        boolean scaleAnyway = false;
        float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight);
        if (minWidth != 0 && minHeight != 0 && (photoW < minWidth || photoH < minHeight)) {
            scaleFactor = Math.max(photoW / minWidth, photoH / minHeight);
            scaleAnyway = true;
        }
        int w = (int)(photoW / scaleFactor);
        int h = (int)(photoH / scaleFactor);
        if (h == 0 || w == 0) {
            return null;
        }

        try {
            return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway);
        } catch (Throwable e) {
            FileLog.e("tmessages", e);
            ImageLoader.getInstance().clearMemory();
            System.gc();
            try {
                return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway);
            } catch (Throwable e2) {
                FileLog.e("tmessages", e2);
                return null;
            }
        }
    }

    public static String getHttpUrlExtension(String url) {
        String ext = null;
        int idx = url.lastIndexOf(".");
        if (idx != -1) {
            ext = url.substring(idx + 1);
        }
        if (ext == null || ext.length() == 0 || ext.length() > 4) {
            ext = "jpg";
        }
        return ext;
    }

    public static void saveMessageThumbs(TLRPC.Message message) {
        TLRPC.PhotoSize photoSize = null;
        if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
            for (TLRPC.PhotoSize size : message.media.photo.sizes) {
                if (size instanceof TLRPC.TL_photoCachedSize) {
                    photoSize = size;
                    break;
                }
            }
        } else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
            if (message.media.video.thumb instanceof TLRPC.TL_photoCachedSize) {
                photoSize = message.media.video.thumb;
            }
        } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
            if (message.media.document.thumb instanceof TLRPC.TL_photoCachedSize) {
                photoSize = message.media.document.thumb;
                for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
                    if (attribute instanceof TLRPC.TL_documentAttributeSticker) {
                        photoSize.location.ext = "webp";
                        break;
                    }
                }
            }
        }
        if (photoSize != null && photoSize.bytes != null && photoSize.bytes.length != 0) {
            if (photoSize.location instanceof TLRPC.TL_fileLocationUnavailable) {
                photoSize.location = new TLRPC.TL_fileLocation();
                photoSize.location.volume_id = Integer.MIN_VALUE;
                photoSize.location.dc_id = Integer.MIN_VALUE;
                photoSize.location.local_id = UserConfig.lastLocalId;
                UserConfig.lastLocalId--;
            }
            File file = FileLoader.getPathToAttach(photoSize, true);
            if (!file.exists()) {
                try {
                    RandomAccessFile writeFile = new RandomAccessFile(file, "rws");
                    writeFile.write(photoSize.bytes);
                    writeFile.close();
                } catch (Exception e) {
                    FileLog.e("tmessages", e);
                }
            }
            TLRPC.TL_photoSize newPhotoSize = new TLRPC.TL_photoSize();
            newPhotoSize.w = photoSize.w;
            newPhotoSize.h = photoSize.h;
            newPhotoSize.location = photoSize.location;
            newPhotoSize.size = photoSize.size;
            newPhotoSize.type = photoSize.type;

            if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
                for (int a = 0; a < message.media.photo.sizes.size(); a++) {
                    if (message.media.photo.sizes.get(a) instanceof TLRPC.TL_photoCachedSize) {
                        message.media.photo.sizes.set(a, newPhotoSize);
                        break;
                    }
                }
            } else if (message.media instanceof TLRPC.TL_messageMediaVideo) {
                message.media.video.thumb = newPhotoSize;
            } else if (message.media instanceof TLRPC.TL_messageMediaDocument) {
                message.media.document.thumb = newPhotoSize;
            }
        }
    }

    public static void saveMessagesThumbs(ArrayList<TLRPC.Message> messages) {
        if (messages == null || messages.isEmpty()) {
            return;
        }
        for (TLRPC.Message message : messages) {
            saveMessageThumbs(message);
        }
    }
}
